'use strict';

setTimeout(process.exit, 3600 * 1000);

var log4js = require('log4js');
var commandLineArgs = require('command-line-args')
var net = require('net');
var request = require('request');

var optionDefinitions = [
  { name: 'ips', alias: 'i', type: String },
  { name: 'api_port', alias: 'p', type: Number },
  { name: 'check_url', alias: 'u', type: String },
  { name: 'check_keyword', alias: 'k', type: String },
];

var logger = log4js.getLogger('default');
logger.level = 'debug';
var options = commandLineArgs(optionDefinitions);

var api_port = options.api_port || 4028;
logger.debug('api_port = %d', api_port);

var check_url = options.check_url || '';
if(!(/http/i.test(check_url) && /\[IP\]/.test(check_url))) {
	logger.error('invalid check_url: %s is not http://user@pass:[IP]/xxxx', check_url);
	process.exit(-1);
}
logger.debug('check_url: %s', check_url);

var check_keyword = options.check_keyword || 'Miner Status';
if(!check_keyword) {
	logger.error('invalid check_keyword');
	process.exit(-1);
}
logger.debug('check_keyword: %s', check_keyword);

if(!options.ips) {
	logger.error('no ips specified (-i), format is x.x.x.x/p,y.y.y.y/q');
	process.exit(-1);
}
var arg_ips = (options.ips || '').split(/,/);
var ips = {};
var failed = false;
for(var arg_ip of arg_ips) {
	var m = arg_ip.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/);
	if(!m) {
		logger.error('invalid ip range: %s', arg_ip);
		failed = true;
		continue;
	}

	var a = +m[1], b = +m[2], c = +m[3], d = +m[4];
	if(a < 0 || a > 255 ||
	   b < 0 || b > 255 ||
	   c < 0 || c > 255 ||
	   d < 0 || d > 255
		) {
		logger.error('invalid ip range: %s', arg_ip);
		failed = true;
		continue;
	}
	var prefix = m[5] * 1;
	if(prefix != 16 && prefix != 24 && prefix != 32) {
		logger.error('invalid ip range: %s, invalid prefix %d which can only be 16,24 or 32', arg_ip, prefix);
		failed = true;
		continue;
	}
	var cc = 0;
	switch(prefix) {
		case 32: ips[ [a,b,c,d].join('.') ] = true; cc = 1; break;
		case 16:
			for(var i = 0;i < 256;i++) {
				for(var j = 1;j < 255;j++) {
					ips[ [a,b,i,j].join('.') ] = true;
					cc++;
				}
			}
			break;
		case 24:
			for(var j = 1;j < 255;j++) {
				ips[ [a,b,c,j].join('.') ] = true;
				cc++;
			}
			break;
	}
	logger.debug('Total %d ips parsed from %s', cc, arg_ip);
}
if(failed) {
	process.exit(-1);
}

ips = Object.keys(ips);
logger.info('Total %d ips specified', ips.length);


var stats = {
	try : 0,
	http_ok: 0,
	data_ok: 0,
	trouble_list: [],
};

function check_ip_cmd(ip, cmd, _cb) {
	var done = false;
	var startAt = new Date();

	function cb() {
		if(timer) {
			clearTimeout(timer);
			timer = null;
		}
		if(done) return;
		done = true;
		if(_cb) _cb({
			ip: ip,
			cmd: cmd,
			data: buf,
			start_at: startAt/1000,
			end_at: new Date()/1000,
		});
	}

	var timer = setTimeout(function() {
		logger.error('%s-%s timeout', ip, cmd);
		buf = null;
		cb();
	}, 1000);

	// 矿机的API端口
	var c = net.connect(api_port, ip, function() {
		//console.log('connected');

		c.write(cmd);
	});

	c.setEncoding('utf-8');

	var buf = '';

	c.on('close', cb);
	c.on('error', console.error);
	c.on('data', function(d) {
		buf += d;
	});
}

function check_ip_url(ip, cb) {
	var url = check_url;
	url = url.replace(/\[IP\]/, ip);

	var m = url.match(/:\/\/([^:]+):([^@]+)@/);

	var req = request;
	if(m) {
		var obj = require('url').parse(url);
		req = function(obj, a,b) {
			a = {
				host: obj.protocol +  '//' + obj.host,
				path: obj.path,
				port: obj.port,
				method: "GET",
				timeout: 30000,
			};
			require('request-digest')(m[1], m[2]).request(a,b);
		}.bind(null,obj);
	}
	req({
		url: url,
		timeout: 30000,
	}, function(err, resp, body) {
		if(err) {
			cb(err);
			return;
		}

		body = body || '';
		if(body.indexOf(check_keyword) >= 0) {
			logger.debug('%s http good', ip);
			cb(null, true);
			return;
		}

		logger.error('%s http failed', ip);
		cb(null, false);
		return;
	});
}

function check_ip(ip, cb) {
	stats.try++;

	check_ip_url(ip, function(err, is_ok) {
		if(err || !is_ok) {
			cb();
			return;
		}

		stats.http_ok++;

		check_ip_cmd(ip, 'pools', function(d) {
			if(d && d.data && d.data.length > 0) {
				stats.data_ok++;
				logger.debug('%s api good', ip);
				cb();
				return;
			}

			logger.error('%s api trouble', ip);
			stats.trouble_list.push(ip);
			cb();
		});
	});
}


function check_all(ips, cb) {
	var startAt = new Date();

	var totIps = ips.length;
	var finishedIps = 0;

	function tryDone() {
		if(finishedIps != totIps) {
			return;
		}

		logger.info('Done');
		stats.cost = (new Date() - initStartedAt)/1000;
		logger.debug('stats: %s', JSON.stringify(stats));
		cb();
	}

	function go() {
		if(ips.length == 0) {
			tryDone();
			return;
		}

		var ip = ips.shift();
		setImmediate(function() {
			check_ip(ip, function(r) {
				finishedIps++;
				logger.debug('finish %d/%d', finishedIps, totIps);
				//setImmediate(go);
				go();
			});
		});
	}

	// 同时发起256路
	for(var i = 0;i < 32;i++) {
		setImmediate(go);
	}
}


var initStartedAt = new Date();
check_all(ips, process.exit);



